Definiowanie zachowania obiektów

Metody instancyjne i klasowe, klasy abstrakcyjne, interfejsy


apohllo@agh.edu.pl

http://apohllo.pl/dydaktyka/programowanie-obiektowe

konsultacje: wtorek 15:30 - 18:00, pokój 4.61

Definiowanie (publicznego) zachowania

In [200]:
enum SpeedUnit {
    KMH, MS
}
In [201]:
class Speed {
    public double value;
    public SpeedUnit unit;
}
In [202]:
class SpaceShip {
    private Speed speed;

    public SpaceShip(){
        this.speed = new Speed();
        this.speed.unit = SpeedUnit.MS;
    }

    public void accelerate(Speed delta){
        if(delta.unit == this.speed.unit && delta.value > 0) {
            this.speed.value += delta.value;
        }
    }
}
In [203]:
class LandVehicle {
    private Speed speed;

    public LandVehicle(){
        this.speed = new Speed();
        this.speed.unit = SpeedUnit.KMH;
    }

    public void accelerate(Speed delta){
        if(delta.unit == this.speed.unit) {
            this.speed.value += delta.value;
        }
    }
}
In [204]:
class Speed {
    private double value;
    private SpeedUnit unit;

    public Speed(double value, SpeedUnit unit){
        this.value = value;
        this.unit = unit;
    }
    
    public Speed add(Speed that){
        if(this.unit == that.unit){
            return new Speed(this.value + that.value, this.unit);
        } else {
            return this; // lepiej rzucić wyjątek lub dokonać konwersji
        }
    }
    
    public boolean isAboveZero(){
        return value > 0;
    }
}
In [206]:
class SpaceShip {
    private Speed speed;

    public SpaceShip(){
        this.speed = new Speed(0, SpeedUnit.MS);
    }

    public void accelerate(Speed delta){
        if(delta.isAboveZero()) {
            this.speed = this.speed.add(delta);
        }
    }
}
In [207]:
class LandVehicle {
    private Speed speed;

    public LandVehicle(){
        this.speed = new Speed(0, SpeedUnit.KMH);
    }

    public void accelerate(Speed delta){
        this.speed = this.speed.add(delta);
    }
}

Modyfikacja zachowania w klasie dziedziczącej

In [208]:
Speed speed0 = new Speed(10, SpeedUnit.KMH);
speed0.toString();
Out[208]:
REPL.$JShell$17BCS$Speed@7cdb2ef8
In [149]:
class Speed {
    private double value;
    private SpeedUnit unit;
    
    public Speed(double value, SpeedUnit unit){
        this.value = value;
        this.unit = unit;
    }
    
    @Override // <- nie jest wymagane, żeby zadziałało
    public String toString(){
        return "" + value + " " + unit;
    }
}
In [146]:
Speed speed1 = new Speed(10, SpeedUnit.KMH); // <---- Speed
speed1.toString();
Out[146]:
10.0 KMH
In [147]:
Object speed2 = new Speed(10, SpeedUnit.KMH); // <---- Object!
speed2.toString();
Out[147]:
10.0 KMH

Rozszerzenie zachowania klasy nadrzędnej

In [151]:
class Speed {
    private double value;
    private SpeedUnit unit;

    public Speed(double value, SpeedUnit unit){
        this.value = value;
        this.unit = unit;
    }

    @Override
    public String toString(){
        return super.toString() + " " + value + " " + unit;
    }
}
In [152]:
Speed speed1 = new Speed(10, SpeedUnit.KMH);
speed1.toString();
Out[152]:
REPL.$JShell$17FT$Speed@3e37b0bf 10.0 KMH

Pola vs. metody

In [154]:
enum SpeedUnit {
    MS, KMH, MA
}
In [158]:
class Speed {
    public double value = 0;
    public SpeedUnit unit = SpeedUnit.MS;
}
In [159]:
class SuperSonicSpeed extends Speed {
    public double value = 10;
    public SpeedUnit unit = SpeedUnit.MA;
}
In [160]:
Speed speed1 = new Speed();
System.out.println(speed1.unit);
System.out.println(speed1.value);
MS
0.0
In [161]:
Speed speed2 = new SuperSonicSpeed(); // <--- zmienna typu Speed
System.out.println(speed2.unit);
System.out.println(speed2.value);
MS
0.0
In [162]:
SuperSonicSpeed speed3 = new SuperSonicSpeed(); // <--- zmienna typu SuperSonicSpeed
System.out.println(speed3.unit);
System.out.println(speed3.value);
MA
10.0

Modyfikator private

In [164]:
class Speed {
    protected static final double MS_TO_KMH_RATIO = 3.6;
    
    protected double value;
    protected SpeedUnit unit;
    
    public Speed(double value, SpeedUnit unit){
        this.value = value;
        this.unit = unit;
    }
    
    public Speed add(Speed that){
        return new Speed(this.value + that.convert(this.unit), this.unit);
    }
    
    public Speed subtract(Speed that){
        return new Speed(this.value - that.convert(this.unit), this.unit);
    }
    
    public String toString() {
        return "" + this.value + " " + this.unit;
    }

    private double convert(SpeedUnit target){
        if(this.unit == SpeedUnit.KMH && target == SpeedUnit.MS){
            return this.value / MS_TO_KMH_RATIO;
        } else if(this.unit == SpeedUnit.MS && target == SpeedUnit.KMH){
            return this.value * MS_TO_KMH_RATIO;
        } else {
            // pozostałe przypadki
            return this.value;
        }
    }   
}
In [166]:
Speed speed1 = new Speed(10, SpeedUnit.MS);
Speed speed2 = new Speed(36, SpeedUnit.KMH);
speed2.add(speed1);
Out[166]:
72.0 KMH
In [167]:
speed1.convert(SpeedUnit.MS);
|   speed1.convert(SpeedUnit.MS);
convert(SpeedUnit) has private access in Speed

In [172]:
class SuperSonicSpeed extends Speed {
    public SuperSonicSpeed(double value){
        super(value, SpeedUnit.MA);
    }
    
    public Speed subtract(SuperSonicSpeed that){
        return new Speed(this.value - that.convert(this.unit), this.unit);
    }

    public double convert(SpeedUnit target){
        return 0;
    }
}
In [173]:
SuperSonicSpeed speed1 = new SuperSonicSpeed(10);
SuperSonicSpeed speed2 = new SuperSonicSpeed(10);
speed1.add(speed2);
Out[173]:
20.0 MA
In [171]:
SuperSonicSpeed speed1 = new SuperSonicSpeed(10);
SuperSonicSpeed speed2 = new SuperSonicSpeed(10);
speed1.subtract(speed2);
Out[171]:
10.0 MA

Modyfikator protected

In [174]:
class Speed {
    private static final double MS_TO_KMH_RATIO = 3.6;
    
    private double value;
    private SpeedUnit unit;
    
    public Speed(double value, SpeedUnit unit){
        this.value = value;
        this.unit = unit;
    }
    
    public Speed add(Speed that){
        return new Speed(this.value + that.convert(this.unit), this.unit);
    }
    
    public Speed subtract(Speed that){
        return new Speed(this.value - that.convert(this.unit), this.unit);
    }
    
    public String toString() {
        return "" + this.value + " " + this.unit;
    }
    
    protected double convert(SpeedUnit target){
        if(this.unit == SpeedUnit.KMH && target == SpeedUnit.MS){
            return this.value / MS_TO_KMH_RATIO;
        } else if(this.unit == SpeedUnit.MS && target == SpeedUnit.KMH){
            return this.value * MS_TO_KMH_RATIO;
        } else {
            return this.value;
        }
    }   
}
In [175]:
class SuperSonicSpeed extends Speed {
    public SuperSonicSpeed(double value){
        super(value, SpeedUnit.MA);
    }

    protected double convert(SpeedUnit target){
        return 0;
    }
}
In [177]:
Speed speed1 = new SuperSonicSpeed(10);
Speed speed2 = new SuperSonicSpeed(10);
speed1.add(speed2);
Out[177]:
10.0 MA

Modyfikator final

In [178]:
class Speed {
    protected final double convert(SpeedUnit target){
        if(this.unit == SpeedUnit.KMH && target == SpeedUnit.MS){
            return this.value / MS_TO_KMH_RATIO;
        } else if(this.unit == SpeedUnit.MS && target == SpeedUnit.KMH){
            return this.value * MS_TO_KMH_RATIO;
        } else {
            return this.value;
        }
    }   
}
In [179]:
class SuperSonicSpeed extends Speed{
    public SuperSonicSpeed(double value){
        super(value, SpeedUnit.MA);
    }

    protected double convert(SpeedUnit target){
        return 0;
    }
}
|           super(value, SpeedUnit.MA);
constructor Speed in class Speed cannot be applied to given types;
  required: no arguments
  found: double,SpeedUnit
  reason: actual and formal argument lists differ in length

|       protected double convert(SpeedUnit target){
|           return 0;
|       }
convert(SpeedUnit) in SuperSonicSpeed cannot override convert(SpeedUnit) in Speed
  overridden method is final

Modyfikator static (metody klasowe)

In [180]:
class Speed {
    private double value = 10;
    
    public String toString(){
        return this.value + "";
    }
    
    public void run(){
        main(null);
    }
    
    public static void main(String[] args){
        Speed speed1 = new Speed();
        System.out.println(speed1);
    }
}
In [181]:
Speed.main(null);
10.0
In [184]:
Speed speed = new Speed();
speed.run();
10.0
In [188]:
class Speed {
    private final double value;
    
    private static Map<Double, Speed> instances = new HashMap<>();

    private Speed(double value){
        this.value = value;
    }

    public static Speed create(double value){
        if(instances.containsKey(value)){
            return instances.get(value);
        } else {
            Speed instance = new Speed(value);
            instances.put(value, instance);
            return instance;
        }
    }
}
In [187]:
Speed speed1 = Speed.create(10);
Speed speed2 = Speed.create(10);

speed1 == speed2
Out[187]:
true

Dziedziczenie a metody statyczne

In [189]:
class Speed {
    private double value;

    private static Map<Double, Speed> instances = new HashMap<>();

    protected Speed(double value){
        this.value = value;
    }
    
    public String toString(){
        return "" + this.value;
    }

    public static Speed create(double value){
        if(instances.containsKey(value)){
            return instances.get(value);
        } else {
            Speed instance = createInstance(value);
            instances.put(value, instance);
            return instance;
        }
    }
    
    protected static Speed createInstance(double value){
        return new Speed(value);
    }
}
In [190]:
class SuperSonicSpeed extends Speed {
    private SuperSonicSpeed(double value){
        super(value);
    }
    
    protected static SuperSonicSpeed createInstance(double value){
        return new SuperSonicSpeed(value * 10);
    }
}
In [191]:
Speed speed = SuperSonicSpeed.create(10);
speed
Out[191]:
10.0

Wzorzec Factory

In [193]:
class Speed {
    private double value;
    
    // modyfikator domyślny dla konstruktora
    Speed(double value){
        this.value = value;
    }
}

class SpeedFactory {
    private Map<Double, Speed> instances = new HashMap<>();
    
    public Speed create(double value){
        if(instances.containsKey(value)){
            return instances.get(value);
        } else {
            Speed instance = new Speed(value);
            instances.put(value, instance);
            return instance;
        }
    }
}
In [194]:
SpeedFactory factory = new SpeedFactory();
Speed speed1 = factory.create(10);
Speed speed2 = factory.create(10);
speed1 == speed2;
Out[194]:
true

Antywzorzec

In [ ]:
class Speed {
    public static Speed toMs(Speed speed1){
        return new Speed(speed1.convert(SpeedUnit.MS), SpeedUnit.MS);
    }
}

Speed speed1 = new Speed(10, SpeedUnit.MS);
Speed.toMs(speed1);

// vs.

speed1.toMs();

Modyfikator abstract

In [195]:
abstract class AbstractSpaceShip {
    private Speed speed;
    
    public AbstractSpaceShip(){
        this.speed = new Speed(0, SpeedUnit.MS);
    }
    
    public abstract void start();
    
    public abstract void land();
}
In [ ]:
class Rocket extends AbstractSpaceShip {
    private Engine engine;
    private Parachute parachute;

    public void start(){
        engine.start();
    }
    
    public void land(){
        parachute.open();
    }
}
In [ ]:
class SpaceShuttle extends AbstractSpaceShip {
    private List<Booster> boosters;
    
    public void start(){
        for(Booster booster : boosters){
            booster.start();
        }
    }
    
    public void land(){
        descend(new Distance(10, DistanceUnit.KM));
    }
    
    private void descend(Distance distance){
        //...
    }
}

In [ ]:
// aliens are coming... 

List<AbstractSpaceShip> spaceFleet = new LinkedList<>();
spaceFleet.add(new Rocket());
spaceFleet.add(new SpaceShuttle());

for(AbstractSpaceShip ship : spaceFleet){
    ship.start();
}

// rescue the World, then...

for(AbstractSpaceShip ship : spaceFleet){
    ship.land();
}

Interfejs (aka międzygębie)

In [196]:
interface ISpaceShip {
    void start();
    void land();
}
In [197]:
class Rocket implements ISpaceShip {
    public void start(){
        //...
    }
    
    public void land(){
        //...
    }
}
In [198]:
class SpaceShuttle implements ISpaceShip {
    public void start(){
        //...
    }

    public void land(){
        //...
    }
}
In [199]:
// aliens are coming... 

List<ISpaceShip> spaceFleet = new LinkedList<>();
spaceFleet.add(new Rocket());
spaceFleet.add(new SpaceShuttle());

for(AbstractSpaceShip ship : spaceFleet){
    ship.start();
}

// rescue the World, then...

for(AbstractSpaceShip ship : spaceFleet){
    ship.land();
}
|   for(AbstractSpaceShip ship : spaceFleet){
incompatible types: ISpaceShip cannot be converted to AbstractSpaceShip
In [ ]:
interface ISpaceShip {
    void start();
    void land();
    // nowa metoda
    void launchMissile();
}

Modyfikator default

In [ ]:
interface ISpaceShip {
    void start();
    void land();
    
    // nowość w Javie 8.0
    default void launchMissile(){
        InterplanetarySystem.out.
            println("Włamujemy się emacsem przez sendmeila");
        InterplanetarySystem.out.
            println("omijając potrójną ścianę ogniową");
    }
}

Dziedziczenie vs. kompozycja

In [ ]:
abstract class Car {
    public void openDoor(DoorSpecification doorSpec){
        doors.get(doorSpec).open();
    }
    
    public abstract void drive();
}
In [ ]:
class GasolineCar extends Car {
    private GasolineEngine engine;
    
    public void drive(){
        engine.deliverGasoline();
        engine.igniteGasoline();
        //...
    }
}
In [ ]:
class ElectricCar extends Car {
    private List<WheelMotor> motors;
    
    public void drive(){
        for(WheelMotor motor : motors){
            motor.rotate();
        }
    }
}
In [ ]:
class Car {
    private IDriveSystem driveSystem;
    
    public Car(IDriveSystem driveSystem){
        this.driveSystem = driveSystem;
    }
    
    public drive(){
        this.driveSystem.drive();
    }
}
In [ ]:
interface IDriveSystem {
    void drive();
}

class ElectricDriveSystem implements IDriveSystem {
    //...
}

class GasolineDriveSystem implements IDriveSystem {
    //...
}
In [ ]:
Car car = new Car(new GasolineDriveSystem());